from fastapi import  Depends, Request,HTTPException, status
from sqlalchemy.future import select
from sqlmodel.ext.asyncio.session import AsyncSession
import json
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
import user_agents
import asyncio

from core.Log import log_admin_event
from core.Config import settings
import models.admin.system.Admin as AdminModel
import models.admin.system.Role as RoleModel

import models.admin.user.user as UserModel
import models.admin.user.third_user as ThirdUserModel

from queues import Rabbitmq, Redis as MQ_Redis
from database.mysql import get_async_session


# 用户端token
oauth2_api_scheme = OAuth2PasswordBearer(tokenUrl="/api/user/getAccessToken")
# 后端管理员token
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/admin/system/admin/getAccessToken")

async def verify_token(token: str,  token_scheme: OAuth2PasswordBearer):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[settings.JWT_ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        return username
    except JWTError as e:
        log_admin_event(f"JWTError: {str(e)}")
        raise credentials_exception

# 定义依赖项以获取当前用户
async def check_token(req: Request, token: str = Depends(oauth2_scheme), session: AsyncSession = Depends(get_async_session)) -> AdminModel.redis_admin:
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    # 这里是获取用户的逻辑
    try:
        payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[settings.JWT_ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError as e:
        log_admin_event(f"JWTError: {str(e)}")
        raise credentials_exception

    # TODO: 从redis中获取到用户信息，包装为AdminModel.Admin返回
    admin = await req.app.state.cache.get(f"{username}:user")
    if admin:
        admin = json.loads(admin)
        return AdminModel.redis_admin(**admin)
    else:
        statement = select(AdminModel.Admin).where(AdminModel.Admin.userName == username)
        result =  await session.exec(statement)
        admin_user:AdminModel.Admin = result.scalars().first()

        # 通过admin_id获取对应得角色
        statement = select(AdminModel.Admin_Role_Link).where(AdminModel.Admin_Role_Link.admin_id == admin_user.id)
        role_result = await session.exec(statement)
        roles = role_result.scalars().all()
        role_ids = [role.role_id for role in roles]
        if 1 in role_ids:
            admin_ids = [1]
        else:
            # 递归查询role_ids下面的所有子角色id
            async def collect_child_ids(role_id):
                child_ids = set()
                statement = select(RoleModel.Role).where(RoleModel.Role.parent_id == role_id)
                result = await session.exec(statement)
                children = result.scalars().all()
                for child in children:
                    child_ids.add(child.id)
                    await collect_child_ids(child.id)
                return child_ids
            # 循环role_ids，查询所有子角色id
            for role_id in role_ids:
                child_ids = await collect_child_ids(role_id)
                role_ids.extend(child_ids)
            # 获取到所有角色的权限ids,并且去重
            menu_statement = select(AdminModel.Admin_Role_Link).where(AdminModel.Admin_Role_Link.role_id.in_(role_ids))
            role_menu_result = await session.exec(menu_statement)
            admin_ids = [role_menu.admin_id for role_menu in role_menu_result.scalars().all()]
            # admin_ids = list(set(admin_ids))

        admin:AdminModel.redis_admin = AdminModel.redis_admin(admin= admin_user, admin_ids=admin_ids)
        # 将用户信息存储到redis中
        await req.app.state.cache.set(f"{username}:user", json.dumps(admin.model_dump()))
    return admin

# 权限验证依赖函数，使用闭包传递自定义参数
def has_permission(auth: str):
    async def dependency(request: Request, user: AdminModel.redis_admin = Depends(check_token)):
        # TODO: 判断是否是超级管理员
        if user.admin.id == 1:
            await user_operation_log(request, user, auth)
            return user

        user_auths = await request.app.state.cache.get(f"{user.admin.userName}:auths")
        if user_auths:
            user_auths = json.loads(user_auths)
            if auth in user_auths:
                # TODO: 日志记录
                await user_operation_log(request, user, auth)
                return user
        detail = f"Operation not permitted. Requires '{auth}' permission."
        await user_operation_log(request, user, auth, detail)
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail=detail,
        )     
      
    return dependency


# 待优化【处理为异步 或者  队列】
async def user_operation_log(request: Request, user: AdminModel.redis_admin, auth: str, message: str = "权限通过"):
     # 获取用户代理信息
    user_agent_str = request.headers.get("user-agent")
    user_agent = user_agents.parse(user_agent_str)
    
    # 获取操作系统和浏览器类型
    os_info = user_agent.os.family
    browser_info = user_agent.browser.family

    log_data = {
        "admin_id": user.admin.id,
        "username": user.admin.userName,
        "action": request.method,
        "path": request.url.path,
        "auth": auth,
        "ip_address": request.client.host,
        "os": os_info,
        "browser": browser_info,
        "message": message
    }
    
    # 如果没有开启rabbitmq则直接存入数据库
    if  settings.RABBITMQ_HOST != None:
        # TODO: 将日志数据发送到rabbitmq
        await Rabbitmq.publish_log(log_data)
    else:
        # 将日志数据添加到缓存，并在需要时入库
        asyncio.create_task(MQ_Redis.log_producer(json.dumps(log_data)))



async def check_api_token(req: Request, token: str = Depends(oauth2_api_scheme), session: AsyncSession = Depends(get_async_session)) -> UserModel.redis_user:
    username = await verify_token(token, oauth2_api_scheme)
    # TODO: 从redis中获取到用户信息，包装为AdminModel.Admin返回
    userinfo = await req.app.state.cache.get(f"gameuser:{username}")
    if userinfo:
        userinfo = json.loads(userinfo)
        return UserModel.redis_user(**userinfo)
    else:
        statement = select(UserModel.Game_User).where(UserModel.Game_User.username == username)
        result =  await session.exec(statement)
        userinfo:UserModel.Game_User = result.scalars().first()
        # 判断用户是否为第三方用户
        third_user_state = select(ThirdUserModel.Game_Third_User).where(ThirdUserModel.Game_Third_User.user_id == userinfo.id)
        result = await session.exec(third_user_state)
        third_user = result.scalars().first()
        if third_user is None:
            third_user = {}

        redis_user = UserModel.redis_user(
            id=userinfo.id,
            nickname=userinfo.nickname,
            username=userinfo.username,
            group_id=userinfo.group_id,
            gender=userinfo.gender,
            bio=userinfo.bio,
            money=userinfo.money,
            score=userinfo.score,
            level=userinfo.level,
            mobile=userinfo.mobile,
            third_user=third_user.model_dump() if hasattr(third_user, 'model_dump') else third_user
        )     
        await req.app.state.cache.set(f"{username}:user", json.dumps(redis_user.model_dump()))
    return redis_user

def has_api_permission(auth: str) -> UserModel.redis_user:
    async def dependency(request: Request, user: UserModel.redis_user = Depends(check_api_token)):
        user_auth = await request.app.state.cache.get(f"gameuser:{user.username}_auths")
        if user_auth:
            user_auth = json.loads(user_auth)
            if auth in user_auth:
                return user
        
        detail = f"Operation not permitted. Requires '{auth}' permission."
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail=detail,
        )
    
    return dependency
